/*****************************************************************************
 *  ENTROPY - emerging network to reduce orwellian potency yield
 *
 *  Copyright (C) 2002 Juergen Buchmueller <pullmoll@stop1984.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 *	$Id: vt.c,v 1.3 2005/07/12 23:12:29 pullmoll Exp $
 *****************************************************************************/
#include "vt.h"
#include "zlib.h"
#include "tools.h"
#include "logger.h"

#define	FONTFILE	"node/my0812.bdf.gz"

#define	CTRL_ACTION	0x0d00ff81
#define	CTRL_ALWAYS	0x0800f501

#define	LAT1_MAP	0
#define	GRAPH_MAP	1
#define	IBMPC_MAP	2
#define	USER_MAP	3

static const uint16_t translations[4][256] = {
{	/* 8-bit Latin-1 mapped to Unicode -- trivial mapping */
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
	0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
	0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
	0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
	0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
	0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
	0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
	0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
	0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
	0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
	0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
	0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
	0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
	0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
	0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
	0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
	0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
	0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
	0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff},
{	/* VT100 graphics mapped to Unicode */
	0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
	0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
	0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
	0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
	0x0028, 0x0029, 0x002a, 0x2192, 0x2190, 0x2191, 0x2193, 0x002f,
	0x2588, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
	0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
	0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
	0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
	0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
	0x2591, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0xf800,
	0xf801, 0x2500, 0xf803, 0xf804, 0x251c, 0x2524, 0x2534, 0x252c,
	0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
	0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
	0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
	0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
	0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
	0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
	0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
	0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
	0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
	0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
	0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
	0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
	0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
	0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
	0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
	0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
	0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff},
{	/* IBM Codepage 437 mapped to Unicode */
	0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
	0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
	0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
	0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
	0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
	0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
	0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
	0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
	0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
	0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
	0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
	0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
	0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
	0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
	0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
	0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
	0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
	0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
	0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
	0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
	0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
	0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
	0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
	0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
	0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
	0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
	0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
	0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
	0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
	0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
	0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
	0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0},
{	/* User mapping -- default to codes for direct font mapping */
	0xf000, 0xf001, 0xf002, 0xf003, 0xf004, 0xf005, 0xf006, 0xf007,
	0xf008, 0xf009, 0xf00a, 0xf00b, 0xf00c, 0xf00d, 0xf00e, 0xf00f,
	0xf010, 0xf011, 0xf012, 0xf013, 0xf014, 0xf015, 0xf016, 0xf017,
	0xf018, 0xf019, 0xf01a, 0xf01b, 0xf01c, 0xf01d, 0xf01e, 0xf01f,
	0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf025, 0xf026, 0xf027,
	0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02d, 0xf02e, 0xf02f,
	0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
	0xf038, 0xf039, 0xf03a, 0xf03b, 0xf03c, 0xf03d, 0xf03e, 0xf03f,
	0xf040, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
	0xf048, 0xf049, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f,
	0xf050, 0xf051, 0xf052, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057,
	0xf058, 0xf059, 0xf05a, 0xf05b, 0xf05c, 0xf05d, 0xf05e, 0xf05f,
	0xf060, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
	0xf068, 0xf069, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f,
	0xf070, 0xf071, 0xf072, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077,
	0xf078, 0xf079, 0xf07a, 0xf07b, 0xf07c, 0xf07d, 0xf07e, 0xf07f,
	0xf080, 0xf081, 0xf082, 0xf083, 0xf084, 0xf085, 0xf086, 0xf087,
	0xf088, 0xf089, 0xf08a, 0xf08b, 0xf08c, 0xf08d, 0xf08e, 0xf08f,
	0xf090, 0xf091, 0xf092, 0xf093, 0xf094, 0xf095, 0xf096, 0xf097,
	0xf098, 0xf099, 0xf09a, 0xf09b, 0xf09c, 0xf09d, 0xf09e, 0xf09f,
	0xf0a0, 0xf0a1, 0xf0a2, 0xf0a3, 0xf0a4, 0xf0a5, 0xf0a6, 0xf0a7,
	0xf0a8, 0xf0a9, 0xf0aa, 0xf0ab, 0xf0ac, 0xf0ad, 0xf0ae, 0xf0af,
	0xf0b0, 0xf0b1, 0xf0b2, 0xf0b3, 0xf0b4, 0xf0b5, 0xf0b6, 0xf0b7,
	0xf0b8, 0xf0b9, 0xf0ba, 0xf0bb, 0xf0bc, 0xf0bd, 0xf0be, 0xf0bf,
	0xf0c0, 0xf0c1, 0xf0c2, 0xf0c3, 0xf0c4, 0xf0c5, 0xf0c6, 0xf0c7,
	0xf0c8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0cc, 0xf0cd, 0xf0ce, 0xf0cf,
	0xf0d0, 0xf0d1, 0xf0d2, 0xf0d3, 0xf0d4, 0xf0d5, 0xf0d6, 0xf0d7,
	0xf0d8, 0xf0d9, 0xf0da, 0xf0db, 0xf0dc, 0xf0dd, 0xf0de, 0xf0df,
	0xf0e0, 0xf0e1, 0xf0e2, 0xf0e3, 0xf0e4, 0xf0e5, 0xf0e6, 0xf0e7,
	0xf0e8, 0xf0e9, 0xf0ea, 0xf0eb, 0xf0ec, 0xf0ed, 0xf0ee, 0xf0ef,
	0xf0f0, 0xf0f1, 0xf0f2, 0xf0f3, 0xf0f4, 0xf0f5, 0xf0f6, 0xf0f7,
	0xf0f8, 0xf0f9, 0xf0fa, 0xf0fb, 0xf0fc, 0xf0fd, 0xf0fe, 0xf0ff}
};

enum {
	ESnormal,
	ESesc,
	EScsi,
	ESgetargs,
	ESgotargs,
	ESfunckey,
	EShash,
	ESsetG0,
	ESsetG1,
	ESpercent,
	ESignore,
	ESnonstd,
	ESpalette
};

/************************************************************************
 *	BDF font handling
 ************************************************************************/
int vt_bdf_free(vt_t *vt)
{
	if (NULL != vt->bdf.bi) {
		vt_bdf_info_t *bi = vt->bdf.bi;
		xfree(bi->version);
		xfree(bi->comment);
		xfree(bi->font);
		xfree(bi->fontname_registry);
		xfree(bi->foundry);
		xfree(bi->family_name);
		xfree(bi->weight_name);
		xfree(bi->slant);
		xfree(bi->setwidth_name);
		xfree(bi->add_style_name);
		xfree(bi->spacing);
		xfree(bi->charset_registry);
		xfree(bi->charset_encoding);
		xfree(bi->copyright);
		xfree(bi->_xmbdfed_info);
		xfree(vt->bdf.bi);
	}
	xfree(vt->bdf.flag);
	xfree(vt->bdf.swidth);
	xfree(vt->bdf.dwidth);
 	xfree(vt->bdf.bbx);
	xfree(vt->bdf.data);
	memset(&vt->bdf, 0, sizeof(vt->bdf));
	return 0;
}

static char *unquote(const char *src)
{
	static char buff[256];
	char *dst = buff;
	if (*src == '"')
		src++;
	while (dst < &buff[255] &&
		*src != '\0' &&
		*src != '"' &&
		*src != '\r' &&
		*src != '\n') {
		*dst++ = *src++;
	}
	*dst = '\0';
	return buff;
}

static int at(const char *dst, const char *src)
{
	int len = strlen(src);
	if (isalnum(dst[len]))
		return -1;
	if (0 != strncmp(dst, src, len))
		return -1;
	return 0;
}

static int append(char **pdst, const char *src)
{
	char *dst;
	if (NULL != *pdst) {
		size_t size = strlen(*pdst) + 1 + strlen(src) + 1;
		dst = (char *)xcalloc(size, sizeof(char));
		pm_snprintf(dst, size, "%s\n%s", *pdst, src);
		xfree(*pdst);
		*pdst = dst;
	} else {
		*pdst = xstrdup(src);
	}
	return 0;
}

int vt_bdf_loadfont(vt_t *vt)
{
	vt_bdf_info_t *bi = vt->bdf.bi;
	gzFile *gz;
	char name[128], line[128];
	int properties = 0;
	int prop_count = 0;
	int startchar = 0;
	int bitmap = 0;
	int indx = 0;
	int code = 0;
	int swap = 0;
	int y = 0;
	FUN("vt_bdf_loadfont");

	vt_bdf_free(vt);
	bi = vt->bdf.bi = (vt_bdf_info_t *)xcalloc(1, sizeof(vt_bdf_info_t));
	if (NULL == (gz = gzopen(vt->fontfile, "r"))) {
		LOG(L_ERROR,("gzopen('%s','%s') call failed (%s)\n",
			vt->fontfile, "r", strerror(errno)));
		return -1;
	}

	while (!gzeof(gz)) {
		if (NULL == gzgets(gz, line, sizeof(line)))
			break;
		if (0 == at(line, "STARTFONT")) {
			append(&bi->version, unquote(line + 10));
			continue;
		}
		if (0 == at(line, "COMMENT")) {
			append(&bi->comment, unquote(line + 8));
			continue;
		}
		if (0 == at(line, "FONT")) {
			append(&bi->font, unquote(line + 5));
			continue;
		}
		if (0 == at(line, "SIZE")) {
			int pixel, res_x, res_y;
			sscanf(line + 5, "%d %d %d", &pixel, &res_x, &res_y);
			bi->size_pixel = pixel;
			bi->size_res_x = res_x;
			bi->size_res_y = res_y;
			continue;
		}
		if (0 == at(line, "FONTBOUNDINGBOX")) {
			int w, h, a, d;
			sscanf(line + 16, "%d %d %d %d", &w, &h, &a, &d);
			bi->bbx.w = (uint8_t)w;
			bi->bbx.h = (uint8_t)h;
			bi->bbx.a = (int8_t)a;
			bi->bbx.d = (int8_t)d;
			continue;
		}
		if (0 == at(line, "STARTPROPERTIES")) {
			properties = 1;
			sscanf(line + 16, "%d", &prop_count);
			continue;
		}
		if (0 != properties) {
			prop_count--;
			if (0 == at(line, "FONTNAME_REGISTRY")) {
				bi->prop_mask |= 1 << PROP_FONTNAME_REGISTRY;
				append(&bi->fontname_registry, unquote(line + 18));
				continue;
			}
			if (0 == at(line, "FOUNDRY")) {
				bi->prop_mask |= 1 << PROP_FOUNDRY;
				append(&bi->foundry, unquote(line + 8));
				continue;
			}
			if (0 == at(line, "FAMILY_NAME")) {
				bi->prop_mask |= 1 << PROP_FAMILY_NAME;
				append(&bi->family_name, unquote(line + 12));
				continue;
			}
			if (0 == at(line, "WEIGHT_NAME")) {
				bi->prop_mask |= 1 << PROP_WEIGHT_NAME;
				append(&bi->weight_name, unquote(line + 12));
				continue;
			}
			if (0 == at(line, "SLANT")) {
				bi->prop_mask |= 1 << PROP_SLANT;
				append(&bi->slant, unquote(line + 6));
				continue;
			}
			if (0 == at(line, "SETWIDTH_NAME")) {
				bi->prop_mask |= 1 << PROP_SETWIDTH_NAME;
				append(&bi->setwidth_name, unquote(line + 14));
				continue;
			}
			if (0 == at(line, "ADD_STYLE_NAME")) {
				bi->prop_mask |= 1 << PROP_ADD_STYLE_NAME;
				append(&bi->add_style_name, unquote(line + 15));
				continue;
			}
			if (0 == at(line, "PIXEL_SIZE")) {
				int value;
				bi->prop_mask |= 1 << PROP_PIXEL_SIZE;
				sscanf(line + 11, "%d", &value);
				bi->pixel_size = value;
				continue;
			}
			if (0 == at(line, "POINT_SIZE")) {
				int value;
				bi->prop_mask |= 1 << PROP_POINT_SIZE;
				sscanf(line + 11, "%d", &value);
				bi->point_size = value;
				continue;
			}
			if (0 == at(line, "RESOLUTION_X")) {
				int value;
				bi->prop_mask |= 1 << PROP_RESOLUTION_X;
				sscanf(line + 13, "%d", &value);
				bi->resolution_x = value;
				continue;
			}
			if (0 == at(line, "RESOLUTION_Y")) {
				int value;
				bi->prop_mask |= 1 << PROP_RESOLUTION_Y;
				sscanf(line + 13, "%d", &value);
				bi->resolution_y = value;
				continue;
			}
			if (0 == at(line, "SPACING")) {
				bi->prop_mask |= 1 << PROP_SPACING;
				append(&bi->spacing, unquote(line + 8));
				continue;
			}
			if (0 == at(line, "AVERAGE_WIDTH")) {
				int value;
				bi->prop_mask |= 1 << PROP_AVERAGE_WIDTH;
				sscanf(line + 14, "%d", &value);
				bi->average_width = value;
				continue;
			}
			if (0 == at(line, "CHARSET_REGISTRY")) {
				bi->prop_mask |= 1 << PROP_CHARSET_REGISTRY;
				append(&bi->charset_registry, unquote(line + 17));
				continue;
			}
			if (0 == at(line, "CHARSET_ENCODING")) {
				bi->prop_mask |= 1 << PROP_CHARSET_ENCODING;
				append(&bi->charset_encoding, unquote(line + 17));
				continue;
			}
			if (0 == at(line, "DEFAULT_CHAR")) {
				int value;
				bi->prop_mask |= 1 << PROP_DEFAULT_CHAR;
				sscanf(line + 13, "%d", &value);
				bi->default_char = value;
				bi->default_char = value;
				continue;
			}
			if (0 == at(line, "FONT_ASCENT")) {
				int value;
				bi->prop_mask |= 1 << PROP_FONT_ASCENT;
				sscanf(line + 12, "%d", &value);
				bi->font_ascent = value;
				continue;
			}
			if (0 == at(line, "FONT_DESCENT")) {
				int value;
				bi->prop_mask |= 1 << PROP_FONT_DESCENT;
				sscanf(line + 13, "%d", &value);
				bi->font_descent = value;
				continue;
			}
			if (0 == at(line, "X_HEIGHT")) {
				int value;
				bi->prop_mask |= 1 << PROP_X_HEIGHT;
				sscanf(line + 9, "%d", &value);
				bi->x_height = value;
				continue;
			}
			if (0 == at(line, "CAP_HEIGHT")) {
				int value;
				bi->prop_mask |= 1 << PROP_CAP_HEIGHT;
				sscanf(line + 11, "%d", &value);
				bi->cap_height = value;
				continue;
			}
			if (0 == at(line, "UNDERLINE_POSITION")) {
				int value;
				bi->prop_mask |= 1 << PROP_UNDERLINE_POSITION;
				sscanf(line + 19, "%d", &value);
				bi->underline_position = value;
				continue;
			}
			if (0 == at(line, "DESTINATION")) {
				int value;
				bi->prop_mask |= 1 << PROP_DESTINATION;
				sscanf(line + 12, "%d", &value);
				bi->destination = value;
				continue;
			}
			if (0 == at(line, "COPYRIGHT")) {
				bi->prop_mask |= 1 << PROP_COPYRIGHT;
				append(&bi->copyright, unquote(line + 10));
				continue;
			}
			if (0 == at(line, "_XMBDFED_INFO")) {
				bi->prop_mask |= 1 << PROP__XBMFED_INFO;
				append(&bi->_xmbdfed_info, unquote(line + 13));
				continue;
			}
			if (0 == at(line, "ENDPROPERTIES")) {
				properties = 0;
				continue;
			}
			if (prop_count > 0) {
				LOG(L_ERROR,("unhandled property: '%s'\n", line));
			}
			continue;
		}
		if (0 == at(line, "CHARS")) {
			int chars;

			bitmap = 0;
			sscanf(line + 6, "%d\n", &chars);
			bi->chars = chars;

			/* set global font width, height and default character */
			vt->bdf.fw = bi->bbx.w;
			vt->bdf.fh = bi->bbx.h;
			vt->bdf.dc = bi->default_char;

			vt->bdf.width = 256 * 32;
			vt->bdf.height = 256 * vt->bdf.fh;
			vt->bdf.bytes_per_row = (vt->bdf.width + 7) / 8;
			vt->bdf.data = (uint32_t *)
				xcalloc(vt->bdf.bytes_per_row, vt->bdf.height);
			continue;
		}
		if (0 == at(line, "STARTCHAR")) {
			sscanf(line + 10, "%s", name);
			startchar = 1;
			bitmap = 0;
			continue;
		}
		if (0 != startchar) {
			if (0 == at(line, "ENCODING")) {
				sscanf(line + 9, "%d", &code);
				if (code < 0 || code > 65535) {
					startchar = 0;
					continue;
				}
				if (NULL == vt->bdf.flag)
					vt->bdf.flag = (uint32_t *)
						xcalloc(65536/32, sizeof(uint32_t));
				if (NULL == vt->bdf.swidth)
					vt->bdf.swidth = (vt_bdf_swidth_t *)
						xcalloc(65536, sizeof(vt_bdf_swidth_t));
				if (NULL == vt->bdf.dwidth)
					vt->bdf.dwidth = (vt_bdf_dwidth_t *)
						xcalloc(65536, sizeof(vt_bdf_dwidth_t));
				if (NULL == vt->bdf.bbx)
					vt->bdf.bbx = (vt_bdf_bbx_t *)
						xcalloc(65536, sizeof(vt_bdf_bbx_t));
			}
			if (0 == at(line, "SWIDTH")) {
				int w, f;
				sscanf(line + 7, "%d %d", &w, &f);
				vt->bdf.swidth[code].w = (uint16_t)w;
				vt->bdf.swidth[code].f = (uint16_t)f;
				continue;
			}
			if (0 == at(line, "DWIDTH")) {
				int w, f;
				sscanf(line + 7, "%d %d", &w, &f);
				vt->bdf.dwidth[code].w = (uint16_t)w;
				vt->bdf.dwidth[code].f = (uint16_t)f;
				continue;
			}
			if (0 == at(line, "BBX")) {
				int w, h, a, d;
				sscanf(line + 4, "%d %d %d %d", &w, &h, &a, &d);
				vt->bdf.bbx[code].w = (uint8_t)w;
				vt->bdf.bbx[code].h = (uint8_t)h;
				vt->bdf.bbx[code].a = (int8_t)a;
				vt->bdf.bbx[code].d = (int8_t)d;
				swap = (w - 1) / 8;
				y = 0;
				continue;
			}
			if (0 == at(line, "BITMAP")) {
				bitmap = 1;
				continue;
			}
			if (0 == at(line, "ENDCHAR")) {
				startchar = 0;
				bitmap = 0;
				indx++;
				continue;
			}
			if (0 != bitmap) {
				uint32_t data;
				size_t offs;

				if (y >= vt->bdf.fh)
					continue;
				data = strtoul(line, NULL, 16);
				switch (swap) {
				case 1:
					data =
						((data & 0x000000ff) << 8) |
						((data & 0x0000ff00) >> 8);
					break;
				case 2:
					data =
						((data & 0x000000ff) << 16) |
						((data & 0x0000ff00)) |
						((data & 0x00ff0000) >> 16);
					break;
				case 3:
					data =
						((data & 0x000000ff) << 24) |
						((data & 0x0000ff00) <<  8) |
						((data & 0x00ff0000) >>  8) |
						((data & 0xff000000) >> 24);
					break;
				}
				offs = ((code / 256) * vt->bdf.fh + y) * 256 + code % 256;
				vt->bdf.data[offs] |= data;
				y++;
			}
		}
	}
	gzclose(gz);

	return 0;
}

/************************************************************************
 *	END OF - BDF font handling
 ************************************************************************/

int vt_out(vt_t *vt, int x, int y, vt_att_t *pa)
{
	int iv, x0, y0, x1, y1, fg, bg;
	uint32_t offs_comb, offs_code;

	if (NULL == pa) {
		pa = &vt->screen[y][x];
	}

	x0 = vt->origin_x + x * vt->bdf.fw;
	y0 = vt->origin_y + y * vt->bdf.fh;
	x1 = x0 + vt->bdf.bbx[pa->code].w;
	y1 = y0 + vt->bdf.bbx[pa->code].h;

	iv = pa->inverse ^ vt->inverse;

	bg = (iv ? pa->fg : pa->bg) | (pa->blink ? 8 : 0);
	fg = (iv ? pa->bg : pa->fg) | (pa->light ? 8 : 0);
	if (0 != pa->cursor) {
		if (0 != vt->cur.on && y == vt->cur.y && x == vt->cur.x) {
			bg ^= iv ?  (vt->cc_mask >>  8) & 15 : (vt->cc_mask >> 12) & 15;
			fg ^= iv ?  (vt->cc_mask >> 12) & 15 : (vt->cc_mask >>  8) & 15;
		} else {
			pa->cursor = 0;
		}
	}

	/* offset of the glyph for code in the bitmap data */
	offs_code = (pa->code % 256) + (pa->code / 256) * vt->bdf.fh * 256;

	/* combined character? */
	if (0x0000 != pa->comb) {
		/* offset of the glyph for comb in the bitmap data */
		offs_comb = (pa->comb % 256) + (pa->comb / 256) * vt->bdf.fh * 256;
		if (vt->bdf.bbx[pa->comb].w > vt->bdf.bbx[pa->code].w) {
			gif_blit(vt->gif, x0, y0, vt->bdf.bbx[pa->comb].w, vt->bdf.fh,
				&vt->bdf.data[offs_comb], 256, fg, bg, GIF_BLITCOPY);
			gif_blit(vt->gif, x0, y0, vt->bdf.bbx[pa->code].w, vt->bdf.fh,
				&vt->bdf.data[offs_code], 256, fg, bg, GIF_BLITSET);
		} else {
			gif_blit(vt->gif, x0, y0, vt->bdf.bbx[pa->code].w, vt->bdf.fh,
				&vt->bdf.data[offs_code], 256, fg, bg, GIF_BLITCOPY);
			gif_blit(vt->gif, x0, y0, vt->bdf.bbx[pa->comb].w, vt->bdf.fh,
				&vt->bdf.data[offs_comb], 256, fg, bg, GIF_BLITSET);
		}
	} else {
		gif_blit(vt->gif, x0, y0, vt->bdf.bbx[pa->code].w, vt->bdf.fh,
			&vt->bdf.data[offs_code], 256, fg, bg, GIF_BLITCOPY);
	}
	return 0;
}

int vt_zap(vt_t *vt, int x0, int y0, int x1, int y1, int code)
{
	int x, y;
	vt_att_t spc;

	spc = vt->att;
	spc.comb = 0;
	spc.code = code;

	for (y = y0; y <= y1; y++) {
		for (x = x0; x <= x1; x++)
			vt_out(vt, x, y, &spc);
		x0 = 0;
		x1 = vt->width - 1;
	}
	return 0;
}

int vt_set_cursor(vt_t *vt, int on)
{
	/* DEC cursor mode off? */
	if (0 == vt->deccm)
		on = 0;
	if (vt->cur.newx >= vt->width)
		on = 0;
	if (vt->cur.on == on)
		return 0;
	vt->cur.on = on;
	/* draw cursor into picture!? */
	return 0;
}

int vt_set_newx(vt_t *vt, int newx)
{
	int wason = vt->cur.on;
	vt_set_cursor(vt, 0);
	vt->cur.newx = newx;
	if (0 != wason)
		vt_set_cursor(vt, 1);
	return 0;
}

int vt_scroll_down(vt_t *vt)
{
	int x, y, xd, yd, xs, ys, w, h;
	vt_att_t *line, spc;

	spc = vt->att;
	spc.code = 0x0020;
	spc.comb = 0x0000;
	spc.width = 1;

	xs = vt->origin_x;
	xd = xs;
	w = vt->width * vt->bdf.fw;
	if (0 == vt->top && vt->height == vt->bottom) {
		/* scroll down the entire screen */
		line = vt->screen[vt->height - 1];
		for (y = vt->height - 1; y > 0; y--)
			vt->screen[y] = vt->screen[y-1];
		vt->screen[y] = line;
		ys = vt->origin_y;
		yd = ys + vt->bdf.fh;
		h = (vt->height - 1) * vt->bdf.fh;
	} else {
		/* scroll down the region */
		line = vt->screen[vt->bottom - 1];
		for (y = vt->bottom - 1; y > vt->top; y--)
			vt->screen[y] = vt->screen[y-1];
		vt->screen[y] = line;
		ys = vt->origin_y + vt->top * vt->bdf.fh;
		yd = ys + vt->bdf.fh;
		h = (vt->bottom - vt->top) * vt->bdf.fh;
	}
	gif_move(vt->gif, xd, yd, xs, ys, w, h);
	for (x = 0; x < vt->width; x++)
		vt_out(vt, x, y, &spc);

	return 0;
}

int vt_scroll_up(vt_t *vt)
{
	int x, y, xd, yd, xs, ys, w, h;
	vt_att_t *line, spc;

	spc = vt->att;
	spc.code = 0x0020;
	spc.comb = 0x0000;
	spc.width = 1;

	xd = vt->origin_x;
	xs = xd;
	w = vt->width * vt->bdf.fw;
	if (0 == vt->top && vt->height == vt->bottom) {
		/* scroll up the entire screen */
		line = vt->screen[0];
		for (y = 0; y < vt->height - 1; y++)
			vt->screen[y] = vt->screen[y+1];
		vt->screen[y] = line;
		yd = vt->origin_y;
		ys = yd + vt->bdf.fh;
		h = (vt->height - 1) * vt->bdf.fh;
	} else {
		/* scroll up the region */
		line = vt->screen[vt->top];
		for (y = vt->top; y < vt->bottom - 1; y++)
			vt->screen[y] = vt->screen[y+1];
		vt->screen[y] = line;
		yd = vt->origin_y + vt->top * vt->bdf.fh;
		ys = yd + vt->bdf.fh;
		h = (vt->bottom - vt->top) * vt->bdf.fh;
	}
	gif_move(vt->gif, xd, yd, xs, ys, w, h);
	for (x = 0; x < vt->width; x++)
		vt_out(vt, x, y, &spc);

	return 0;
}

int vt_ri(vt_t *vt)
{
	if (vt->cur.y == vt->top)
		return vt_scroll_down(vt);
	if (vt->cur.y > 0)
		vt->cur.y -= 1;
	return 0;
}

int vt_lf(vt_t *vt)
{
	if (vt->cur.y + 1 == vt->bottom)
		return vt_scroll_up(vt);
	if (vt->cur.y < vt->height - 1)
		vt->cur.y += 1;
	return 0;
}

int vt_cr(vt_t *vt)
{
	vt_set_newx(vt, 0);
	vt->cur.x = 0;
	return 0;
}

int vt_gotoxy(vt_t *vt, int x, int y)
{
	int miny, maxy;
	if (x < 0) {
		vt_set_newx(vt, 0);
		vt->cur.x = 0;
	} else {
		if (x >= vt->width) {
			vt_set_newx(vt, vt->width - 1);
		} else {
			vt_set_newx(vt, x);
		}
		vt->cur.x = vt->cur.newx;
	}
	if (0 != vt->decom) {
		miny = vt->top;
		maxy = vt->bottom;
	} else {
		miny = 0;
		maxy = vt->height;
	}
	if (y < miny) {
		vt->cur.y = miny;
	} else {
		vt->cur.y = (y >= maxy) ? maxy - 1 : y;
	}
	return 0;
}

int vt_gotoy(vt_t *vt, int y)
{
	vt->cur.x = vt->cur.newx;
	return vt_gotoxy(vt, vt->cur.x, y);
}

int vt_gotoxay(vt_t *vt, int x, int y)
{
	return vt_gotoxy(vt, x, 0 == vt->decom ? y : vt->top + y);
}

int vt_CSI_lh(vt_t *vt, int on)
{
	int i;
	FUN("vt_CSI_lh");

	vt->cnt++;
	for (i = 0; i < vt->cnt; i++) {
		if (0 != vt->ques) {
			switch (vt->par[i]) {
			case 1:		/* cursor keys send ^[0x ... ^[[x */
				vt->curkeys = on;
				break;
			case 3:		/* 80/132 mode */
				/* how? */
				break;
			case 5:		/* inverted screen on/of */
				vt->inverse = on;
				break;
			case 6:		/* origin relative/absolute */
				vt->decom = on;
				vt_gotoxay(vt, 0, 0);
				break;
			case 7:		/* auto wrap on/off */
				vt->decawm = on;
				break;
			case 8:		/* keyboard autorepeat on/off */
				vt->decarm = on;
				break;
			case 9:		/* report mouse on/off */
				vt->repmouse = on;
				break;
			case 25:	/* cursor on off */
				vt->deccm = on;
				break;
			default:
				LOG(L_ERROR,("invalid <CSI>?%d%c\n",
					vt->par[i], on ? 'h' : 'l'));
			}
		} else {
			switch (vt->par[i]) {
			case 3:		/* monitor display controls */
				vt->dspctrl = on;
				break;
			case 4:		/* insert mode on/off */
				vt->decim = on;
				break;
			case 20:	/* keyboard enter = CRLF/LF */
				vt->deccr = on;
				break;
			default:
				LOG(L_ERROR,("invalid <CSI>%d%c\n",
					vt->par[i], on ? 'h' : 'l'));
			}
		}
	}
	return 0;
}

int vt_CSI_J(vt_t *vt, int n)
{
	FUN("vt_CSI_J");

	vt->cur.x = vt->cur.newx;
	switch (n) {
	case 0:		/* erase cursor to end of display */
		vt_zap(vt, vt->cur.x, vt->cur.y, vt->width-1, vt->height-1, 0x0020);
		break;
	case 1:		/* erase from start of display to cursor */
		vt_zap(vt, 0, 0, vt->cur.x, vt->cur.y, 0x0020);
		break;
	case 2:		/* erase entire display */
		vt_zap(vt, 0, 0, vt->width-1, vt->height-1, 0x0020);
		break;
	default:
		LOG(L_ERROR,("invalid range %d\n", n));
	}
	return 0;
}

int vt_CSI_K(vt_t *vt, int n)
{
	FUN("vt_CSI_K");

	vt->cur.x = vt->cur.newx;
	switch (n) {
	case 0:		/* erase cursor to end of line */
		vt_zap(vt, vt->cur.x, vt->cur.y, vt->width-1, vt->cur.y, 0x0020);
		break;
	case 1:		/* erase from start of line to cursor */
		vt_zap(vt, 0, vt->cur.y, vt->cur.x, vt->cur.y, 0x0020);
		break;
	case 2:		/* erase entire line */
		vt_zap(vt, 0, vt->cur.y, vt->width-1, vt->cur.y, 0x0020);
		break;
	default:
		LOG(L_ERROR,("invalid range %d\n", n));
	}
	return 0;
}

int vt_CSI_L(vt_t *vt, int n)
{
	int top;
	FUN("vt_CSI_L");

	vt->cur.x = vt->cur.newx;
	top = vt->top;
	vt->top = vt->cur.y;
	while (n-- > 0)
		vt_scroll_down(vt);
	vt->top = top;

	return 0;
}

int vt_CSI_M(vt_t *vt, int n)
{
	int top;
	FUN("vt_CSI_L");

	vt->cur.x = vt->cur.newx;
	top = vt->top;
	vt->top = vt->cur.y;
	while (n-- > 0)
		vt_scroll_up(vt);
	vt->top = top;

	return 0;
}

int vt_CSI_AT(vt_t *vt, int n)
{
	int x;
	vt_att_t spc;
	FUN("vt_CSI_AT");

	/* insert n blanks at the cursor position */

	vt->cur.x = vt->cur.newx;
	while (n-- > 0) {
		for (x = vt->width - 1; x > vt->cur.x; x--)
			vt_out(vt, x, vt->cur.y, &vt->screen[vt->cur.y][x-1]);
		spc = vt->att;
		spc.code = 0x0020;
		spc.comb = 0;
		spc.width = 1;
		vt_out(vt, vt->cur.x, vt->cur.y, &spc);
	}

	return 0;
}

int vt_CSI_P(vt_t *vt, int n)
{
	int x;
	vt_att_t spc;
	FUN("vt_CSI_P");

	/* delete n characters at the cursor position */

	vt->cur.x = vt->cur.newx;
	while (n-- > 0) {
		for (x = vt->cur.x; x < vt->width - 1; x++)
			vt_out(vt, x, vt->cur.y, &vt->screen[vt->cur.y][x+1]);
		spc = vt->att;
		spc.code = 0x0020;
		spc.comb = 0;
		spc.width = 1;
		vt_out(vt, vt->width - 1, vt->cur.y, &spc);
	}

	return 0;
}

int vt_CSI_X(vt_t *vt, int n)
{
	int x;
	vt_att_t spc;
	FUN("vt_CSI_X");

	/* earse n characters at the cursor position */

	vt->cur.x = vt->cur.newx;
	spc = vt->att;
	spc.code = 0x0020;
	spc.comb = 0;
	spc.width = 1;
	vt_out(vt, vt->width - 1, vt->cur.y, &spc);
	for (x = vt->cur.x; x < vt->cur.x + n && x < vt->width; x++)
		vt_out(vt, x, vt->cur.y, &spc);

	return 0;
}

int vt_CSI_m(vt_t *vt)
{
	int i;
	FUN("vt_CSI_m");

	vt->cnt++;
	for (i = 0; i < vt->cnt; i++) {
		switch (vt->par[i]) {
		case 0:		/* reset to defaults */
			vt->att.fg = vt->def.fg;
			vt->att.bg = vt->def.bg;
			vt->att.light = vt->def.light;
			vt->att.inverse = vt->def.inverse;
			vt->att.underline = vt->def.underline;
			vt->att.blink = vt->def.blink;
			vt->att.conceal = vt->def.conceal;
			break;
		case 1:		/* highlight on */
			vt->att.light = 1;
			break;
		case 2:		/* highlight off */
			vt->att.light = 0;
			break;
		case 4:		/* underline on */
			vt->att.underline = 1;
			break;
		case 5:		/* blinking on */
			vt->att.blink = 1;
			break;
		case 7:		/* inverse on */
			vt->att.inverse = 1;
			break;
		case 8:		/* concealed on */
			vt->att.conceal = 1;
			break;
		case 10:	/* select primary font, no control chars, reset togmeta */
			vt->trans = translations[vt->att.charset ?
				vt->att.setG1 : vt->att.setG0];
			vt->dspctrl = 0;
			vt->togmeta = 0;
			break;
		case 11:	/* select alternate font, display control chars */
			vt->trans = translations[IBMPC_MAP];
			vt->dspctrl = 1;
			vt->togmeta = 0;
			break;
		case 12:	/* select alternate font, display high bit chars  */
			vt->trans = translations[IBMPC_MAP];
			vt->dspctrl = 1;
			vt->togmeta = 1;
			break;
		case 21:	/* highlight on */
			vt->att.light = 1;
			break;
		case 22:	/* highlight on (another variant?) */
			vt->att.light = 1;
			break;
		case 24:	/* underline off */
			vt->att.underline = 0;
			break;
		case 25:	/* blinking off */
			vt->att.blink = 0;
			break;
		case 27:	/* inverse off */
			vt->att.inverse = 0;
			break;
		case 30: case 31: case 32: case 33:
		case 34: case 35: case 36: case 37:
			/* set foreground color */
			vt->att.fg = vt->par[i] - 30;
			break;
		case 38:	/* default color, underline on */
			vt->att.fg = vt->def.fg;
			vt->att.underline = 1;
			break;
		case 39:	/* default color, underline off */
			vt->att.fg = vt->def.fg;
			vt->att.underline = 0;
			break;
		case 40:	/* default background */
			vt->att.bg = vt->def.bg;
			break;
		case 41: case 42: case 43:
		case 44: case 45: case 46: case 47:
			/* set background color */
			vt->att.bg = vt->par[i] - 40;
			break;
		case 49:	/* default background */
			vt->att.bg = vt->def.bg;
			break;
		default:
			LOG(L_ERROR,("invalid <CSI>%dm\n", vt->par[i]));
		}
	}
	return 0;
}

int vt_CSI_LINUX(vt_t *vt)
{
	FUN("vt_CSI_LINUX");

	switch (vt->par[0]) {
	case 1:		/* set underline color */
		vt->uc = vt->par[1] & 7;
		break;
	case 2:		/* set half-bright color */
		vt->hc = vt->par[1] & 7;
		break;
	case 8:		/* store attributes as default */
		vt->def = vt->att;
		break;
	case 9:		/* set blanking interval */
		vt->blank_time = (vt->par[1] < 60 ? vt->par[1] : 60) * 60;
		break;
	case 10:	/* set bell pitch */
		vt->bell_pitch = vt->cnt >= 1 ? vt->par[1] : 750;
		break;
	case 11:	/* set bell duration */
		vt->bell_duration = vt->cnt >= 1 ?
			vt->par[1] < 2000 ? vt->par[1] : 2000 : 125;
		break;
	case 12:	/* bring the specified console to the front */
#if	0
		if (vt->par[1] >= 1 && vt->par[1] <= 8)
			vt_console(vt, vt->par[1]);
#endif
		break;
	case 13:	/* unblank the screen */
		break;
	case 14:	/* set VESA powerdown interval */
		vt->vesa_time = (vt->par[1] < 60 ? vt->par[1] : 60) * 60;
		break;
	}
	return 0;
}

int vt_palette_reset(vt_t *vt)
{
	int i;
	FUN("vt_palette_reset");

	GIF_RGB_SET(&vt->pal[ 0],   0,  0,  0);
	GIF_RGB_SET(&vt->pal[ 1], 166,  0,  0);
	GIF_RGB_SET(&vt->pal[ 2],   0,166,  0);
	GIF_RGB_SET(&vt->pal[ 3], 166, 85,  0);
	GIF_RGB_SET(&vt->pal[ 4],   0,  0,166);
	GIF_RGB_SET(&vt->pal[ 5], 166,  0,166);
	GIF_RGB_SET(&vt->pal[ 6],   0,166,166);
	GIF_RGB_SET(&vt->pal[ 7], 166,166,166);
	GIF_RGB_SET(&vt->pal[ 8],  85, 85, 85);
	GIF_RGB_SET(&vt->pal[ 9], 240, 85, 85);
	GIF_RGB_SET(&vt->pal[10],  85,240, 85);
	GIF_RGB_SET(&vt->pal[11], 240,240, 85);
	GIF_RGB_SET(&vt->pal[12],  85, 85,240);
	GIF_RGB_SET(&vt->pal[13], 240, 85,240);
	GIF_RGB_SET(&vt->pal[14],  85,240,240);
	GIF_RGB_SET(&vt->pal[15], 240,240,240);
	if (NULL != vt->gif) {
		for (i = 0; i < 16; i++)
			gif_set_pal(vt->gif, i, &vt->pal[i]);
	}
	return 0;
}

int vt_reset(vt_t *vt, int width, int height)
{
	int y;
	FUN("vt_reset");

	if (0 == xvalid(vt->screen) &&
		(width != vt->width || height != vt->height)) {
		for (y = 0; y < vt->height; y++)
			if (0 == xvalid(vt->screen[y]))
				xfree(vt->screen[y]);
		xfree(vt->screen);
	}

	if (0 != xvalid(vt->screen)) {
		/* allocate the screen buffer */
		vt->screen = (vt_att_t **)xcalloc(height, sizeof(vt_att_t *));
		for (y = 0; y < height; y++)
			vt->screen[y] = (vt_att_t *)xcalloc(width, sizeof(vt_att_t));
	}
	vt->width = width;
	vt->height = height;

	vt->top = 0;
	vt->bottom = vt->height;

	vt_palette_reset(vt);
	memset(vt->tabstop, 0x01, sizeof(vt->tabstop));

	vt->cnt = 0;
	vt->state = ESnormal;
	vt->ques = 0;
	vt->inverse = 0;
	vt->togmeta = 0;
	vt->deccm = 1;
	vt->decim = 0;
	vt->decom = 0;
	vt->deccr = 1;
	vt->curkeys = 0;
	vt->decawm = 1;
	vt->decarm = 1;
	vt->repmouse = 0;
	vt->uc = 0;
	vt->hc = 0;
	vt->bell_pitch = 750;
	vt->bell_duration = 125;
	vt->blank_time = 60 * 60;
	vt->vesa_time = 60 * 60;
	vt->utf_mode = 0;
	vt->utf_char = 0;
	vt->utf_count = 0;
	vt->cc_mask = 0x7700;
	vt->cc_save = vt->cc_mask;

	vt->def.code = 0x0020;
	vt->def.comb = 0x0000;
	vt->def.fg = 0;
	vt->def.bg = 7;
	vt->def.light = 0;
	vt->def.inverse = 0;
	vt->def.underline = 0;
	vt->def.blink = 1;
	vt->def.conceal = 0;
	vt->def.cursor = 0;
	vt->def.charset = 0;
	vt->def.setG0 = LAT1_MAP;
	vt->def.setG1 = GRAPH_MAP;
	vt->def.width = 1;
	vt->att = vt->def;

	vt->trans = translations[LAT1_MAP];

	vt->cur.x = 0;
	vt->cur.y = 0;
	vt->cur.newx = 0;
	vt->cur.phase = 0;
	vt->cur.on = 0;

	return 0;
}

int vt_save(vt_t *vt)
{
	vt->cur_saved = vt->cur;
	vt->att_saved = vt->att;
	return 0;
}

int vt_restore(vt_t *vt)
{
	vt_gotoxy(vt, vt->cur_saved.x, vt->cur_saved.y);
	vt->att = vt->att_saved;
	vt->trans = translations[vt->att.charset ? vt->att.setG1 : vt->att.setG0];
	return 0;
}

int vt_putch(vt_t *vt, int ch)
{
	int glyph;
	uint32_t tc = 0, ctrl, tabpos, tabbit;
	int ch2 = 0;
	FUN("vt_putch");

	for (;;) {
		ctrl = 0 != vt->dspctrl ? CTRL_ALWAYS : CTRL_ACTION;

		if (0 != vt->utf_mode) {
			if (0 != (ch & 0x80)) {
				if (vt->utf_count > 0) {
					if (0x80 == (ch & 0xc0)) {
						vt->utf_char = (vt->utf_char << 6) | (ch & 0x3f);
						LOG(L_DEBUG,("UTF:%02x -> %04x (%d were left)\n",
							ch, vt->utf_char, vt->utf_count));
						if (--vt->utf_count == 0) {
							tc = vt->utf_char;
							vt->utf_char = 0;
							if (tc < 0x80 || tc > 0xffff) {
								tc = 0xfffd;
							}
						}
					} else {
						LOG(L_ERROR,("UTF:%02x -> %04x (%d; invalid cont.)\n",
							ch, vt->utf_char, vt->utf_count));
						vt->utf_count = 0;
						vt->utf_char = 0;
						tc = 0xfffd;
					}
				} else if (0xc0 == (ch & 0xe0)) {
					vt->utf_count = 1;
					vt->utf_char = ch & 0x1f;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else if (0xe0 == (ch & 0xf0)) {
					vt->utf_count = 2;
					vt->utf_char = ch & 0x0f;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else if (0xf0 == (ch & 0xf8)) {
					vt->utf_count = 3;
					vt->utf_char = ch & 0x07;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else if (0xf8 == (ch & 0xfc)) {
					vt->utf_count = 4;
					vt->utf_char = ch & 0x03;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else if (0xfc == (ch & 0xfe)) {
					vt->utf_count = 5;
					vt->utf_char = ch & 0x01;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else if (0xfc == ch) {
					vt->utf_count = 6;
					vt->utf_char = ch;
					LOG(L_DEBUG,("UTF:%02x -> %04x (expect %d more)\n",
						ch, vt->utf_char, vt->utf_count));
				} else {
					LOG(L_ERROR,("UTF:%02x -> %04x (%d; invalid lead in)\n",
						ch, vt->utf_char, vt->utf_count));
					vt->utf_count = 0;
					vt->utf_char = 0;
					tc = 0xfffd;
				}
			} else {
				if (vt->utf_count > 0) {
					LOG(L_ERROR,("UTF:%02x -> %04x (%d; missing cont.)\n",
						ch, vt->utf_char, vt->utf_count));
					vt->utf_count = 0;
					vt->utf_char = 0;
					ch2 = ch;
					tc = 0xfffd;
				} else {
					vt->utf_count = 0;
					vt->utf_char = ch;
					tc = ch;
					LOG(L_DEBUG,("UTF:%02x -> %04x (CS0 character)\n",
						ch, vt->utf_char));
				}
			}
			if (vt->utf_count > 0) {
				return 0;
			}
			glyph = (tc > 0) &&
				(ch >= 32 || (0 == vt->utf_mode && 0 == ((ctrl >> ch) & 1))) &&
				(ch != 127 || 0 != vt->dspctrl) &&
				(ch != 127+27 || 0 != vt->utf_mode);
			if (0 != glyph && 0 == vt->bdf.bbx[tc].w) {
				LOG(L_ERROR,("UTF:%04x -> fffd (not available)\n",
					tc));
				tc = 0xfffd;
			}
		} else {
			ch = (uint8_t)ch;
			tc = vt->trans[vt->togmeta ? ch | 0x80 : ch];
			if (ch > 0x7f || vt->togmeta) {
				LOG(L_DEBUG,("ch:%02x -> UTF:%04x\n",
					ch, tc));
			}
			glyph = (tc > 0) &&
				(ch >= 32 || (0 == vt->utf_mode && 0 == ((ctrl >> ch) & 1))) &&
				(ch != 127 || 0 != vt->dspctrl) &&
				(ch != 127+27 || 0 != vt->utf_mode);
		}

		if (0 == glyph || ESnormal != vt->state)
			break;

		/* is this a combining character? */
		if (0 == ucs_wcwidth(tc)) {
			vt_att_t chr = vt->screen[vt->cur.y][vt->cur.x];
			chr.comb = (uint16_t)tc;
			if (vt->bdf.bbx[tc].w > vt->bdf.fw)
				chr.width = 2;
			vt_out(vt, vt->cur.x, vt->cur.y, &chr);
			vt_set_newx(vt, vt->cur.x + chr.width);
		} else {
			if (vt->cur.newx >= vt->width) {
				if (0 != vt->decawm) {
					vt_cr(vt);
					vt_lf(vt);
				} else {
					vt_set_newx(vt, vt->width - 1);
				}
			}
			vt->cur.x = vt->cur.newx;
			if (0 != vt->decim)
				vt_CSI_AT(vt, 1);
			vt->att.code = (uint16_t)tc;
			vt->att.comb = 0;
			vt->att.width = 1;
			if (vt->bdf.bbx[tc].w > vt->bdf.fw)
				vt->att.width = 2;
			vt_out(vt, vt->cur.x, vt->cur.y, &vt->att);
			vt_set_newx(vt, vt->cur.x + vt->att.width);
		}
		if (0 == ch2) {
			return 0;
		}
		ch = ch2;
		ch2 = 0;
	}

	switch (ch) {
	case 0:		/* NUL */
		return 0;
	case 7:		/* BEL */
		return 0;
	case '\b':	/* BSP */
		vt->cur.x = vt->cur.newx;
		if (vt->cur.x > 0)
			vt_set_newx(vt, vt->cur.x - 1);
		vt->cur.x = vt->cur.newx;
		return 0;
	case '\t':	/* TAB */
		if (vt->cur.newx >= vt->width) {
			/* DEC auto wrap mode? */
			if (0 != vt->decawm) {
				vt_cr(vt);
				vt_lf(vt);
			} else {
				vt->cur.newx = vt->width;
			}
		}
		if (vt->cur.newx >= vt->width)
			return 0;
		vt->cur.x = vt->cur.newx;
		while (vt->cur.x < vt->width - 1) {
			vt_att_t spc = vt->att;
			spc.code = 0x0020;
			spc.comb = 0x0000;
			spc.width = 1;
			vt_out(vt, vt->cur.x, vt->cur.y, &spc);
			vt_set_newx(vt, vt->cur.x + 1);
			vt->cur.x = vt->cur.newx;
			tabpos = vt->cur.x / 8;
			tabbit = vt->cur.x % 8;
			if (tabpos >= sizeof(vt->tabstop) ||
				0 != (vt->tabstop[tabpos] & (1 << tabbit)))
				break;
		}
		return 0;
	case '\n':	/* LF */
		/* DEC auto carriage return? */
		if (0 != vt->deccr) {
			vt_cr(vt);
		}
		vt_lf(vt);
		return 0;
	case 11:	/* VT */
		/* DEC auto carriage return? */
		if (0 != vt->deccr) {
			vt_cr(vt);
		}
		vt_lf(vt);
		return 0;
	case 12:	/* FF */
		/* DEC auto carriage return? */
		if (0 != vt->deccr) {
			vt_cr(vt);
		}
		vt_lf(vt);
		return 0;
	case '\r':	/* CR */
		vt_cr(vt);
		return 0;
	case 14:	/* SI */
		vt->att.charset = 1;
		vt->dspctrl = 1;
		vt->trans = translations[vt->att.setG1];
		return 0;
	case 15:	/* SO */
		vt->att.charset = 0;
		vt->dspctrl = 0;
		vt->trans = translations[vt->att.setG0];
		return 0;
	case 24:	/* CAN */
		vt->state = ESnormal;
		return 0;
	case 26:	/* SUB */
		vt->state = ESnormal;
		return 0;
	case 27:	/* ESC */
		vt->state = ESesc;
		return 0;
	case 128+27:	/* CSI */
		vt->state = EScsi;
		return 0;
	}
	switch (vt->state) {
	case ESesc:	/* ESC state */
		vt->state = ESnormal;
		switch (ch) {
		case '[':	/* CSI */
			vt->state = EScsi;
			break;
		case ']':	/* Linux special escape */
			vt->state = ESnonstd;
			break;
		case '%':	/* Unicode extension */
			vt->state = ESpercent;
			break;
		case 'N':	/* select GS 1 */
			vt->att.charset = 1;
			vt->dspctrl = 1;
			vt->trans = translations[vt->att.setG1];
			break;
		case 'O':	/* select GS 0 */
			vt->att.charset = 0;
			vt->dspctrl = 1;
			vt->trans = translations[vt->att.setG0];
			break;
		case 'D':	/* cursor down */
			vt_lf(vt);
			break;
		case 'E':	/* cursor down */
			vt_cr(vt);
			vt_lf(vt);
			break;
		case 'M':	/* cursor up */
			vt_ri(vt);
			break;
		case 'H':	/* set tabstop */
			if (vt->cur.newx < vt->width) {
				tabbit = vt->cur.newx % 8;
				tabpos = vt->cur.newx / 8;
				if (tabpos < sizeof(vt->tabstop))
					vt->tabstop[tabpos] |= 1 << tabbit;
			}
			break;
		case 'Z':	/* respond ID */
			break;
		case '7':	/* save cursor */
			vt_save(vt);
			break;
		case '8':	/* restore cursor */
			vt_restore(vt);
			break;
		case '(':	/* GS 0 */
			vt->state = ESsetG0;
			break;
		case ')':	/* GS 1 */
			vt->state = ESsetG1;
			break;
		case '#':	/* Hash */
			vt->state = EShash;
			break;
		case 'c':	/* terminal reset */
			vt_reset(vt, vt->width, vt->height);
			vt_CSI_J(vt, 2);
			break;
		case '>':	/* numeric keypad */
			break;
		case '=':	/* application keypad */
			break;
		}
		break;
	case ESnonstd:
		vt->state = ESnormal;
		switch (ch) {
		case 'P':	/* set palette */
			vt->state = ESpalette;
			memset(vt->par, 0, sizeof(vt->par));
			vt->cnt = 0;
			break;
		case 'R':	/* reset palette */
			vt_palette_reset(vt);
			break;
		}
		break;
	case ESpalette:	/* transfer palette entry */
		if (isxdigit(ch)) {
			vt->par[vt->cnt] = ch > '9' ? toupper(ch) - 'A' + 10 : ch - '0';
			if (++vt->cnt == 7) {
				LOG(L_DEBUG,("set palette #%x R:%x%x G:%x%x B:%x%x\n",
					vt->par[0],
					vt->par[1], vt->par[2],
					vt->par[3], vt->par[4],
					vt->par[5], vt->par[6]));
				GIF_RGB_SET(&vt->pal[vt->par[0]],
					(vt->par[1] <<  4) | (vt->par[2] <<  0),
					(vt->par[3] << 12) | (vt->par[4] <<  8),
					(vt->par[5] << 20) | (vt->par[6] << 16));
				if (NULL != vt->gif) {
					gif_set_pal(vt->gif, vt->par[0], &vt->pal[vt->par[0]]);
				}
				vt->state = ESnormal;
			}
		}
		break;
	case EScsi:	/* CSI state */
		vt->state = ESgetargs;
		memset(vt->par, 0, sizeof(vt->par));
		vt->cnt = 0;
		if (ch == '[') {
			vt->state = ESfunckey;
			return 0;
		}
		vt->ques = ch == '?';
		if (0 != vt->ques) {
			return 0;
		}
	case ESgetargs:
		switch (ch) {
		case ';':
			if (vt->cnt < 10)
				vt->cnt++;
			return 0;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			if (vt->cnt < 10)
				vt->par[vt->cnt] = vt->par[vt->cnt] * 10 + ch - '0';
			return 0;
		default:
			vt->state = ESgotargs;
		}
	case ESgotargs:
		vt->state = ESnormal;
		switch (ch) {
		case 'h':	/* set state to on */
			vt_CSI_lh(vt, 1);
			return 0;
		case 'l':	/* set state to off */
			vt_CSI_lh(vt, 0);
			return 0;
		case 'c':	/* cursor mode? */
			if (0 != vt->ques) {
				if (0 != vt->par[0])
					vt->cursor_type =
						vt->par[0] + (vt->par[1] << 8) + (vt->par[2] << 16);
				else
					vt->cursor_type = 0;
			}
			break;
		case 'm':	/* complement mode? */
			if (0 != vt->ques) {
				if (0 != vt->par[0])
					vt->cc_mask = (vt->par[0] << 8) | vt->par[1];
				else
					vt->cc_mask = vt->cc_save;
			}
			break;
		case 'n':	/* CSI response requests */
			if (0 == vt->ques) {
				switch (vt->par[0]) {
				case 5:
					LOG(L_DEBUG,("<CSI>5n respond status\n"));
					break;
				case 6:
					LOG(L_DEBUG,("<CSI>6n respond cursor\n"));
					break;
				default:
					LOG(L_DEBUG,("<CSI>%dn respond cursor\n", vt->par[0]));
					break;
				}
			}
			break;
		}
		if (0 != vt->ques) {
			vt->ques = 0;
			return 0;
		}
		switch (ch) {
		case 'G':	/* set cursor column */
		case '`':	/* set cursor column (variant) */
			if (vt->par[0] > 0) vt->par[0] -= 1;
			vt_gotoxy(vt, vt->par[0], vt->cur.y);
			break;
		case 'A':	/* n times cursor up */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoy(vt, vt->cur.y - vt->par[0]);
			break;
		case 'B':	/* n times cursor down */
		case 'e':
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoy(vt, vt->cur.y + vt->par[0]);
			break;
		case 'C':	/* n times cursor right */
		case 'a':
			vt->cur.x = vt->cur.newx;
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoxy(vt, vt->cur.x + vt->par[0], vt->cur.y);
			break;
		case 'D':	/* n times cursor left */
			vt->cur.x = vt->cur.newx;
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoxy(vt, vt->cur.x - vt->par[0], vt->cur.y);
			break;
		case 'E':	/* n rows down */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoxy(vt, 0, vt->cur.y + vt->par[0]);
			break;
		case 'F':	/* n rows up */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_gotoxy(vt, 0, vt->cur.y - vt->par[0]);
			break;
		case 'd':	/* set cursor row */
			vt->cur.x = vt->cur.newx;
			if (vt->par[0] > 0) vt->par[0] -= 1;
			vt_gotoxay(vt, vt->cur.x, vt->par[0]);
			break;
		case 'H':	/* set cursor address */
		case 'f':	/* set cursor address (variant) */
			if (vt->par[0] > 0) vt->par[0] -= 1;
			if (vt->par[1] > 0) vt->par[1] -= 1;
			vt_gotoxay(vt, vt->par[1], vt->par[0]);
			break;
		case 'J':	/* clear screen */
			vt_CSI_J(vt, vt->par[0]);
			break;
		case 'K':	/* clear line */
			vt_CSI_K(vt, vt->par[0]);
			break;
		case 'L':	/* insert line */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_CSI_L(vt, vt->par[0]);
			break;
		case 'M':	/* delete line */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_CSI_M(vt, vt->par[0]);
			break;
		case 'P':	/* delete character */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_CSI_P(vt, vt->par[0]);
			break;
		case 'c':	/* respond ID */
			if (0 == vt->par[vt->cnt]) {
				LOG(L_DEBUG,("respond ID\n"));
			} else {
				LOG(L_DEBUG,("invalid respond ID command\n"));
			}
			break;
		case 'g':	/* reset tabstop */
			switch (vt->par[0]) {
			case 0:
				LOG(L_DEBUG,("reset tabstop (%d)\n", vt->cur.newx));
				tabbit = vt->cur.newx % 8;
				tabpos = vt->cur.newx / 8;
				if (tabpos < sizeof(vt->tabstop))
					vt->tabstop[tabpos] &= ~(1 << tabbit);
				break;
			case 3:
				LOG(L_DEBUG,("reset all tabstop marks\n"));
				memset(vt->tabstop, 0, sizeof(vt->tabstop));
				break;
			default:
				LOG(L_DEBUG,("invalid reset tabstop command\n"));
			}
			break;
		case 'm':	/* CSI m */
			vt_CSI_m(vt);
			break;
		case 'q':
			/* DECLL (set leds) */
			break;
		case 'r':	/* set region */
			if (0 == vt->par[0]) vt->par[0] = 1;
			if (0 == vt->par[1]) vt->par[1] = vt->height;
			if (vt->par[0] < vt->par[1] && vt->par[1] <= vt->height) {
				vt->top = vt->par[0] - 1;
				vt->bottom = vt->par[1];
				vt_gotoxay(vt, 0, 0);
			}
			break;
		case 's':	/* save cursor */
			vt_save(vt);
			break;
		case 'u':	/* restore cursor */
			vt_restore(vt);
			break;
		case 'X':	/* blank n characters */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_CSI_X(vt, vt->par[0]);
			break;
		case '@':	/* inser n characters */
			if (0 == vt->par[0]) vt->par[0] = 1;
			vt_CSI_AT(vt, vt->par[0]);
			break;
		case ']':	/* Linux console setterm commands */
			vt_CSI_LINUX(vt);
			break;
		}
		break;
	case ESpercent:
		vt->state = ESnormal;
		switch (ch) {
		case '@':	/* defined in ISO 2022 */
			LOG(L_DEBUG,("UTF-8 mode off\n"));
			vt->utf_mode = 0;
			break;
		case 'G':	/* preliminary official escape code */
			LOG(L_DEBUG,("UTF-8 mode on\n"));
			vt->utf_mode = 1;
			break;
		case '8':	/* retained for compatibility */
			LOG(L_DEBUG,("UTF-8 mode on\n"));
			vt->utf_mode = 1;
			break;
		}
		break;
	case ESsetG0:
		vt->state = ESnormal;
		switch (ch) {
		case '0':	/* graphics map */
			vt->att.setG0 = GRAPH_MAP;
			break;
		case 'B':	/* latin 1 map */
			vt->att.setG0 = LAT1_MAP;
			break;
		case 'U':	/* IBMPC map */
			vt->att.setG0 = IBMPC_MAP;
			break;
		case 'K':	/* user map */
			vt->att.setG0 = USER_MAP;
			break;
		}
		if (0 == vt->att.charset)
			vt->trans = translations[vt->att.setG0];
		break;
	case ESsetG1:
		vt->state = ESnormal;
		switch (ch) {
		case '0':	/* graphics map */
			vt->att.setG1 = GRAPH_MAP;
			break;
		case 'B':	/* latin 1 map */
			vt->att.setG1 = LAT1_MAP;
			break;
		case 'U':	/* IBMPC map */
			vt->att.setG1 = IBMPC_MAP;
			break;
		case 'K':	/* user map */
			vt->att.setG1 = USER_MAP;
			break;
		}
		if (1 == vt->att.charset)
			vt->trans = translations[vt->att.setG1];
		break;
	case EShash:
		vt->state = ESnormal;
		switch (ch) {
		case '8':	/* DEC screen alignment test */
			break;
		}
		break;
	case ESfunckey:
		vt->state = ESnormal;
		break;
	}
	return 0;
}

int vt_write(vt_t *vt, const char *buff, int len)
{
	int i;
	FUN("vt_write");
	for (i = 0; i < len; i++)
		vt_putch(vt, buff[i]);
	gif_finish(vt->gif, 0);
	return len;
}

int vt_printf(vt_t *vt, const char *fmt, ...)
{
	char *buff = NULL;
	va_list ap;
	int len;
	FUN("vt_printf");

	va_start(ap, fmt);
	len = pm_vasprintf(&buff, fmt, ap);
	va_end(ap);

	if (len <= 0)
		return len;

	len = vt_write(vt, buff, len);
	xfree(buff);

	return len;
}

int vt_destroy(vt_t *vt)
{
	int y;
	FUN("vt_destroy");

	if (0 != xvalid(vt))
		return -1;

	/* if we allocated a GIF image (always, except for errors) */
	if (NULL != vt->gif) {
		/* last frame of the GIF file */
		gif_finish(vt->gif, 1);
		vt->gif = NULL;
	}

	/* free the screen buffer */
	if (0 == xvalid(vt->screen)) {
		for (y = 0; y < vt->height; y++)
			if (0 == xvalid(vt->screen[y]))
				xfree(vt->screen[y]);
		xfree(vt->screen);
	}

	/* free the font */
	vt_bdf_free(vt);

	/* free the terminal */
	xfree(vt);

	return 0;
}

/* write to stdout */ 
static int write_stdout(void *user, const void *buff, size_t size)
{
	(void)user;
	return write(1, buff, size);
}

vt_t *vt_create(int width, int height)
{
	vt_t *vt;
	int rc;
	FUN("vt_create");

	/* allocate the terminal */
	vt = (vt_t *)xcalloc(1, sizeof(vt_t));

	/* reset the terminal to the defaults */
	vt_reset(vt, width, height);

	/* load a BDF font */
	vt->fontfile = (char *)xcalloc(MAXPATHLEN, sizeof(char));
	pm_snprintf(vt->fontfile, MAXPATHLEN, "%s%s",
		g_conf->progpath, FONTFILE);
	if (0 != (rc = vt_bdf_loadfont(vt))) {
		LOG(L_ERROR,("vt_bdf_loadfont(%p) call failed (%s)\n",
			vt, strerror(errno)));
		vt_destroy(vt);
		return NULL;
	}

	vt->gif = gif_create(vt->bdf.fw * width + 2*4, vt->bdf.fh * height + 2*4,
		4, vt->pal, NULL, write_stdout);
	if (NULL == vt->gif) {
		LOG(L_ERROR,("gif_create() call failed (%s)\n",
			strerror(errno)));
		vt_destroy(vt);
		return NULL;
	}
	gif_filled_rectangle(vt->gif,
		0, 0, vt->gif->scr.width - 1, vt->gif->scr.height - 1,
		15);
	gif_3d_rectangle(vt->gif,
		0, 0, vt->gif->scr.width - 1, vt->gif->scr.height - 1,
		8, 7, 2);
	vt->origin_x = 4;
	vt->origin_y = 4;

	return vt;
}
